package com.kamistoat.meimeistore.product.web;

import com.kamistoat.common.To.SecKillTo.SecKillProductCacheTo;
import com.kamistoat.meimeistore.product.entity.CategoryEntity;
import com.kamistoat.meimeistore.product.feign.SecKillFeignService;
import com.kamistoat.meimeistore.product.service.CategoryService;
import com.kamistoat.meimeistore.product.vo.thymeleaf.Catalog2Vo;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Controller
public class IndexController {
    @Autowired
    CategoryService categoryService;
    @Autowired
    SecKillFeignService secKillFeignService;

    // 操作redis的客户端类
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    // Redisson锁
    @Autowired
    RedissonClient redissonClient;

    /**
     * 访问 / /index.html 都访问首页
     * 同时查出一级菜单
     *
     * @param model SpringMVC的库类，用于动态渲染页面
     * @return 页面的名字，用于跳转页面
     */
    @GetMapping({"/", "/index.html"})
    public String indexPage(Model model) {
        // 只查询一级分类，在Category服务层创建这个方法
        List<CategoryEntity> categoryEntityList = categoryService.getTopCategorys();

        model.addAttribute("categorys", categoryEntityList);  // 向交互体里放入名为 categorys 的数据
        return "index";  // 会自动加上thymeleaf:prefix和thymeleaf:suffix配置的前后缀
    }

    /**
     * 访问秒杀活动列表页。
     * @return
     */
    @GetMapping("/seckillList.html")
    public String seckillListPage(Model model){
        List<List<SecKillProductCacheTo>> futureSecKillSessionList = secKillFeignService.getFutureSecKillSessionList();
        if(futureSecKillSessionList != null && futureSecKillSessionList.size() > 0){
            // 不会前端，只能手动删除最近的那个秒杀，剩下的进行展示
            futureSecKillSessionList.remove(0);
        }
        model.addAttribute("futureSecKillSessionList", futureSecKillSessionList);
        return "seckill_list";
    }


    /**
     * 用@ResponseBody注解声明返回一个json，而不是页面跳转
     *
     * @return
     */
    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        Map<String, List<Catalog2Vo>> catalogJson = categoryService.getCatalogJson();
        return catalogJson;
    }

    /**
     * 配合Sentinel流控。当流量过大时，进入等待页面
     * @return
     */
    @GetMapping("/blockWait.html")
    public String blockWait(){
        return "blockWait";
    }

    /**
     * 一个简单服务，用于测试nginx和gateway中间件的性能
     * 用@ResponseBody规定返回值就是hello
     * 如果去掉@ResponseBody，则返回页面hello.html
     *
     * @return 返回String
     */
    @ResponseBody
    @GetMapping("/hello")
    public String hello() {
        // 直接调用RedissonClient.getLock("任意指定的锁定名称")
        RLock mylock = redissonClient.getLock("my-lock");
        // 执行加锁
        mylock.lock();
        System.out.println("加锁成功" + Thread.currentThread().getId());
        try {
            System.out.println("用try包含业务逻辑，无论是否成功，都要执行解锁" +
                    Thread.currentThread().getId());
            Thread.sleep(10000);  // 10s，模拟程序的超长执行
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            // 执行解锁
            mylock.unlock();
            System.out.println("解锁成功" + Thread.currentThread().getId());
        }
        return "hello";
    }

    /**
     * 测试写锁
     *
     * @return
     */
    @ResponseBody
    @GetMapping("/write")
    public String writeLock() {
        // 获取一个读写锁。同样，只要锁的名字是一样的就代表是同一把锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        // 加写锁，没有写成一个式子，方便解锁？
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        String uuid = "";
        try {
            // 模拟一个业务，这里就是向redis中存放一个数据，当然干啥都行
            uuid = UUID.randomUUID().toString();
            Thread.sleep(10000);
            stringRedisTemplate.opsForValue().set("writeLock", uuid, 30, TimeUnit.SECONDS);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            // 无论业务代码是否成功都执行解锁
            // Redisson会删除掉rw-lock锁，留下一个最新的writeLock
            rLock.unlock();
        }
        return uuid;
    }

    /**
     * 测试读锁
     *
     * @return
     */
    @ResponseBody
    @GetMapping("/read")
    public String readLock() {
        // 获取一个读写锁。同样，只要锁的名字是一样的就代表是同一把锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        // 加读锁，没有写成一个式子，方便解锁？
        RLock rLock = readWriteLock.readLock();
        rLock.lock();
        String s = "";
        try {
            s = stringRedisTemplate.opsForValue().get("writeLock");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            rLock.unlock();
        }
        return s;
    }

    /**
     * 测试闭锁
     */
    @ResponseBody
    @GetMapping("/close")
    public String closeLock() throws InterruptedException {
        // 获取一个闭锁
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        // 使用.trySetCount(Long)来声明这个闭锁将会等待多少个线程，设置这个闭锁的计数器
        door.trySetCount(5);
        // 使用.await()等待闭锁完成
        // 设置了最大等待时间，如果超过20s就不等待了
        boolean await = door.await(20, TimeUnit.SECONDS);
        if (await) {
            return "闭锁内线程全部结束……";
        } else {
            // 如果是超过等待时间，则要手动删除闭锁
            door.delete();
            return "超出闭锁最大等待时间";
        }
    }

    /**
     * 用于在闭锁中执行的线程
     */
    @ResponseBody
    @GetMapping("/go/{id}")
    public String gogogo(@PathVariable("id") Long id) {
        // 获取一个闭锁，只要锁的名字是一样的，获取的就是同一把锁
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        /**
         * 此处执行业务逻辑代码
         */
        // 一旦要结束这个线程了，就要将闭锁的计数器减一
        // 这里只有这一个线程，所以这个线程得执行五次，闭锁才能释放
        door.countDown();
        return id + "线程结束…";
    }

    /**
     * 测试信号量锁
     * 停车
     */
    @ResponseBody
    @GetMapping("/park")
    public String park() throws InterruptedException {
        // 获取一个信号量锁
        RSemaphore park = redissonClient.getSemaphore("park");
        // 为信号锁设置信号容量。当然这个park()方法是业务方法，所以手动去redis加的这个键值
        // park.trySetPermits(3);
        // 尝试占领一个空信号量，如果不成功则会阻塞在这里，一直进行尝试，并设置最大等待10秒
        boolean tryAcquire = park.tryAcquire(1, 30, TimeUnit.SECONDS);
        if (tryAcquire) {
            return "停车成功……";
        } else {
            return "超出最大等待时间……";
        }
    }

    /**
     * 测试信号量锁
     * 离开
     */
    @ResponseBody
    @GetMapping("leave")
    public String leave() {
        // 获取一个信号量锁
        RSemaphore park = redissonClient.getSemaphore("park");
        // 释放一个信号量
        park.release(1);
        return "离开车库……";
    }
}






























